package pCloud.client;

import java.io.BufferedReader;
import java.io.FileReader;
import java.net.InetSocketAddress;
import java.util.ArrayList;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.SimpleLayout;

import pCloud.config.PCloudConfig;
import pCloud.config.PCloudDirectRequestConfig;
import pCloud.config.PCloudRequestConfig;
import pCloud.exception.InvalidClientRequest;
import pCloud.message.ClientRequest;
import pCloud.message.DatabaseMessage;
import pCloud.message.DatabaseRequest;
import pCloud.message.DatabaseSlidingWindowRequest;
import pCloud.message.Message;
import pCloud.message.PolicyFile;
import pCloud.message.PolicyManagementRequest;
import pCloud.message.RequestedResource;
import pCloud.service.ClientProxyServerProtHandle;
import pCloud.transport.Communicator;
import pCloud.transport.SocketCommunicator;

public class DirectClient {
	Connector connector;
	InetSocketAddress proxyServerAddress;

	Communicator communicator;
	int clientId;
	Logger logger;

	String configFile = PCloudConfig.DEFAULT_CONFIG_FILE;
	InetSocketAddress localAddress;

	public DirectClient(int clientId, String configFile) throws Exception {
		this.clientId = clientId;
		this.communicator = new SocketCommunicator();
		this.configFile = configFile;
		init();
		startConnection();
	}

	void init() throws Exception {
		PCloudConfig.init(this.configFile);

		logger = initLog("Client" + clientId);
		communicator.init(logger);

		String[] server = PCloudConfig.getString(
				PCloudDirectRequestConfig.SERVER).split(" ");
		InetSocketAddress serverAdd = new InetSocketAddress(server[0],
				new Integer(server[1]).intValue());

		connector = new ClientProxyServerConnector(serverAdd.getHostName(),
				serverAdd.getPort(), this.communicator);

		proxyServerAddress = serverAdd;
		System.out.println("Server address = " + this.proxyServerAddress);
	}

	Logger initLog(String name) {
		Logger logger = Logger.getLogger(name);
		SimpleLayout layout = new SimpleLayout();

		FileAppender appender = null;
		try {
			appender = new FileAppender(layout,
					PCloudConfig.getString(PCloudConfig.PCLOUD_CLIENT_LOG),
					false);
		} catch (Exception e) {
			System.err.println("Error initializing log file for " + name);
			e.printStackTrace();
		}

		logger.addAppender(appender);
		return logger;
	}

	private void startConnection() throws Exception {
		connector.connect();
		this.communicator.addConnection(connector.protocolHandler);
		this.localAddress = (InetSocketAddress) connector.protocolHandler
				.getTransportHanlde().getLocalAddress();
	}

	/**
	 * Not going through the proxy, but connect and send DatabaseMessage
	 * directly to the specified server.
	 * 
	 * The input file contains simply the server addresses and the SQL query
	 * 
	 * @throws Exception
	 */
	public void requestDirectFromServer(boolean isNoData, String outputFile)
			throws Exception {
		PCloudDirectRequestConfig.init(this.configFile);
		InetSocketAddress isa = PCloudDirectRequestConfig.getServerAddress();
		String query = PCloudDirectRequestConfig.getQuery();

		// setting the printing data option on the protocol handler.
		((ClientProxyServerProtHandle) connector.protocolHandler)
				.setPrintingDataOption(isNoData);
		((ClientProxyServerProtHandle) connector.protocolHandler)
				.setTimeLogFile(outputFile);

		DatabaseMessage dm = new DatabaseRequest(query);
		Message msg = new Message(this.localAddress, this.proxyServerAddress,
				dm, Message.CLIENT_TO_SERVER);

		// System.out.println("In "+this.configFile+", trying to send to "+this.proxyServerAddress);
		((ClientProxyServerProtHandle) connector.protocolHandler)
				.initStartTime();
		communicator.send(this.proxyServerAddress, msg);
	}

	public void requestSlidingWindowDirect(boolean isNoData, String outputFile)
			throws Exception {
		PCloudDirectRequestConfig.init(this.configFile);
		InetSocketAddress isa = PCloudDirectRequestConfig.getServerAddress();

		// setting the printing data option on the protocol handler.
		((ClientProxyServerProtHandle) connector.protocolHandler)
				.setPrintingDataOption(isNoData);
		((ClientProxyServerProtHandle) connector.protocolHandler)
				.setTimeLogFile(outputFile);

		String[] cols = PCloudDirectRequestConfig.getString(
				PCloudDirectRequestConfig.COLUMNS).split(" ");
		ArrayList<String> columns = new ArrayList<String>();
		for (int i = 0; i < cols.length; i++)
			columns.add(cols[i]);
		String tableId = PCloudDirectRequestConfig
				.getString(PCloudDirectRequestConfig.TABLE);
		String start = PCloudDirectRequestConfig
				.getString(PCloudDirectRequestConfig.START_DATE);
		String end = PCloudDirectRequestConfig
				.getString(PCloudDirectRequestConfig.END_DATE);
		String function = PCloudDirectRequestConfig
				.getString(PCloudDirectRequestConfig.AGGREGATION);
		int windowSize = new Integer(
				PCloudDirectRequestConfig
						.getString(PCloudDirectRequestConfig.SIZE)).intValue();
		int step = new Integer(
				PCloudDirectRequestConfig
						.getString(PCloudDirectRequestConfig.STEP)).intValue();

		DatabaseMessage dm = new DatabaseSlidingWindowRequest(columns, tableId,
				start, end, function, windowSize, step);
		Message msg = new Message(this.localAddress, this.proxyServerAddress,
				dm, Message.CLIENT_TO_SERVER);

		// System.out.println("In "+this.configFile+", trying to send to "+this.proxyServerAddress);
		((ClientProxyServerProtHandle) connector.protocolHandler)
				.initStartTime();
		communicator.send(this.proxyServerAddress, msg);
	}

	public static void main(String[] args) throws Exception {

		if (args.length < 1)
			throw new InvalidClientRequest("Config file needed");

		System.out.println("Start_time " + System.currentTimeMillis());

		if (args[0].equals("-help")) {
			System.out
					.println("DirectClient <configFile> [-noData] [-tOption <fileName>]: querying raw data");
			System.out
					.println("DirectClient -slidingWindow <configFile> [-noData] [-tOption <fileName>]: sliding window query");
			System.out
					.println("DirectClient -twoConfigs <confileFile> [-noData] [-tOption <fileName>]");
			return;
		}
		// if there's two config files, start them concurrently.
		else if (args[0].equals("-twoConfigs")) {
			boolean isNoData = true;
			String timeOutputFile = null;
			for (int i = 1; i < args.length; i++) {
				if (args[i].equals("-noData"))
					isNoData = false;
				if (args[i].equals("-tOutput"))
					timeOutputFile = args[i + 1];
			}

			// The config file contains path to the two direct-query file
			BufferedReader reader = new BufferedReader(new FileReader(args[1]));
			DirectThread t1 = new DirectThread(0, reader.readLine(), isNoData,
					timeOutputFile);
			DirectThread t2 = new DirectThread(0, reader.readLine(), isNoData,
					timeOutputFile);
			t1.start();
			t2.start();
		} else if (args[0].equals("-slidingWindow")) {
			boolean isNoData = true;
			String timeOutputFile = null;
			for (int i = 1; i < args.length; i++) {
				if (args[i].equals("-noData"))
					isNoData = false;
				if (args[i].equals("-tOutput"))
					timeOutputFile = args[i + 1];
			}

			DirectClient client = new DirectClient(0, args[1]);
			client.requestSlidingWindowDirect(isNoData, timeOutputFile);
			return;
		} else {
			boolean isNoData = true;
			String timeOutputFile = null;
			for (int i = 1; i < args.length; i++) {
				if (args[i].equals("-noData"))
					isNoData = false;
				if (args[i].equals("-tOutput"))
					timeOutputFile = args[i + 1];
			}

			DirectClient client = new DirectClient(0, args[0]);
			client.requestDirectFromServer(isNoData, timeOutputFile);
			return;
		}
	}
}

class DirectThread extends Thread {
	DirectClient client;
	boolean isNoData;
	String timeOutput;

	public DirectThread(int val, String configFile, boolean isNoData,
			String timeOutput) throws Exception {
		client = new DirectClient(val, configFile);
		this.isNoData = isNoData;
		this.timeOutput = timeOutput;
	}

	public void run() {
		try {
			client.requestDirectFromServer(isNoData, timeOutput);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
